package com.blacksee.bluetoothpoker.network;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

import com.blacksee.bluetoothpoker.network.messages.ExistingTablesMessage;

//TODO add kill client method
public class BluePokerClientImpl implements DiscoveryListener, IMessageSender {

	//TODO extract it in another place
	private static final UUID DEVICE_UUID = new UUID("F0E0D0C0B0A000908070605040302010",false);
	
	private static final int ATTR_SET = 0x0100;
	
	private static final String SERVICE_NAME = "Blue Poker";

	private LocalDevice localDevice = null;

	private DiscoveryAgent discoveryAgent = null;

	private RemoteDevice remoteDevice = null;

	private Vector discoveredDevices = null;

	private Vector discoveredServices = null;

	private StreamConnection serverConnection = null;

	private boolean deviceReady = false;

	public boolean isDeviceReady() {
		return deviceReady;
	}

	public BluePokerClientImpl(){
		try {
			localDevice = LocalDevice.getLocalDevice();

			System.out.println("LOCAL ADDRESS: " + localDevice.getBluetoothAddress());//TODO remove

			discoveredDevices = new Vector();

			discoveredServices = new Vector();

			System.out.println("Created CLIENT"); //TODO remove
		} catch (BluetoothStateException e) {
			e.printStackTrace();
		}
	}

	public void startClient(){

		System.out.println("Client Started"); //TODO remove

		prepareConnection();

		System.out.println("Connection prepared");//TODO remove

		synchronized (this){
			try {
				// devices search is over
				wait();

				System.out.println("Start searching services for remote device: " + remoteDevice);//TODO remove

				searchServices();

				// services search is over
				wait();


				System.out.println("Bye Bye!!");
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (BluetoothStateException e) {
				e.printStackTrace();
			} 
		}
	}

	private void searchServices() throws BluetoothStateException {
		int attrSet[] = new int[]{0x0100};//TODO what does this mean

		if (discoveredDevices.size() > 0){
			System.out.println("Devices: " + discoveredDevices.size()); //TODO remove
			for (int i=0;i<discoveredDevices.size();i++){
				RemoteDevice remote = ((RemoteDevice)discoveredDevices.elementAt(i));

				String address = remote.getBluetoothAddress();
				//	TODO try to find another solution to identify the master
				if (address == null || !address.equals("0000000DECAF")){
					System.out.println(">>>>>>>>>>>>>>>>>>>>>FURTHER"); //TODO remove
					continue;
				} else {
					System.out.println("@@@@@@@@@@@@@@ GOOD ONE"); // TODO remove
				}
				
				discoveryAgent.searchServices(new int[] {ATTR_SET}, new UUID[]{DEVICE_UUID}, (RemoteDevice)discoveredDevices.elementAt(i), this);
				//System.out.println(".....SEARCH "); //TODO remove
			}
		}

	}

	private void prepareConnection() {
		try{
			discoveryAgent = localDevice.getDiscoveryAgent();

			discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);
		} catch(BluetoothStateException e){
			e.printStackTrace();
		}
	}

	public void deviceDiscovered(RemoteDevice foundDevice, DeviceClass deviceClass) {
		//TODO should we handle the case when more then one devices is found???
		//TODO limit the search only to mobile phones and handheld devices

		// TODO check if the device is a server 
		if (discoveredDevices.indexOf(foundDevice) == -1){
			discoveredDevices.addElement(foundDevice);
		}


		System.out.println("Device discovered"); //TODO remove
	}

	public void inquiryCompleted(int arg0) {
		synchronized (this) {
			notify();
		}

		System.out.println("Inquiry completed " + discoveredDevices.size());//TODO remove
	}

	public void serviceSearchCompleted(int arg0, int arg1) {
		synchronized (this) {
			notify();
		}

		deviceReady = true;

		System.out.println("Service search completed " + deviceReady);//TODO remove
		
		ExistingTablesMessage mssg = new ExistingTablesMessage(extractTables());
		NetCommunicator.getInstance().receiveMssage(mssg);
	}

	public void servicesDiscovered(int arg0, ServiceRecord[] serviceRecords) {
		System.out.println("SERVICES DISCOVERED: " + serviceRecords.length);//TODO remove

		synchronized (discoveredServices) {
			for (int i=0; i<serviceRecords.length;i++){
				String url = serviceRecords[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);

				DataElement nameElement = (DataElement)serviceRecords[i].getAttributeValue(0x0100);
				
				if ((nameElement != null) &&
						(nameElement.getDataType() == DataElement.STRING)) {
					
					String name = (String)nameElement.getValue();
					
					System.out.println(">>>>>>>>>>>>>>>>>>>>SERVICE NAME: " + name); 
					
				} else {
					System.out.println(">>>>>>>>>>>>>>>>>>>> NONE NAME");
				}
				

				discoveredServices.addElement(serviceRecords[i]);
			}
		}
	}


	/**
	 * 
	 */
	public void createConnectionToServer(int tableId) throws BlueNetException {
		try{
			ServiceRecord serviceRecord = (ServiceRecord)discoveredServices.elementAt(tableId);
			String serverURL = serviceRecord.getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);

			System.out.println("Connect to " + serverURL); //TODO remove

			remoteDevice 	 = serviceRecord.getHostDevice();

			serverConnection = (StreamConnection)Connector.open(serverURL);
			
			// TODO: start listening for incoming messages from server
			ServerProcessor processor = new ServerProcessor(this, serverConnection);
			Thread thread = new Thread(processor);
			thread.start();

			System.out.println("Connected to server"); //TODO remove
		} catch (IOException e) {
			e.printStackTrace();
			throw new BlueNetException(e.getMessage(), tableId);
		}
	}

	/**
	 * @return - a vector containing TableBean elements
	 */
	public Vector extractTables(){
		Vector tables = new Vector();

		for (int i=0;i<discoveredServices.size();i++){
			ServiceRecord sr = (ServiceRecord)discoveredServices.elementAt(i);
				
			String name = extractServiceName(sr);
			
			TableBean t = new TableBean(name,i);
			
			tables.addElement(t);
		}

		return tables;
	}
	
	private String extractServiceName(ServiceRecord sr) {
		DataElement nameElement = (DataElement)sr.getAttributeValue(0x0100);

		if ((nameElement != null) &&
				(nameElement.getDataType() == DataElement.STRING)) {
			
			String name = (String)nameElement.getValue();
			
			return name;
		}
		
		return null;
	}

	private void receiveMessage(int serverID, String message) {
		NetCommunicator.getInstance().receiveMssage(message, serverID);
	}

	public void broadcastMessage(String message) {
		try {
			DataOutputStream output = (DataOutputStream)serverConnection.openDataOutputStream();

			output.writeUTF("0" + message);
			output.flush();
		} catch(IOException e){
			e.printStackTrace();
		}
	}

	public void sendMessage(int receiverId, String message) {
		try {
			System.out.println("SEND MESSAGE TO SERVER: " + message); //TODO remove
			
			DataOutputStream output = (DataOutputStream)serverConnection.openDataOutputStream();

			output.writeUTF("1" + message);
			output.flush();
		} catch(IOException e){
			e.printStackTrace();
		}
	}
	
	private static class ServerProcessor implements Runnable {

		private BluePokerClientImpl parent = null;

		private StreamConnection connection = null;

		private DataInputStream serverDataInputStream = null;

		public ServerProcessor(BluePokerClientImpl parent, StreamConnection connection){
			this.parent     = parent;
			this.connection = connection;

			prepareCommunication();
		}

		private void prepareCommunication() {
			try {
				serverDataInputStream  = connection.openDataInputStream();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		public void run(){
			while (true){
				try {
					while (serverDataInputStream.available() == 0){
						// wait
					}

					String message = serverDataInputStream.readUTF();

					synchronized (parent) {
						// remove the first char of the message to be received
						String mssg;
						if (message != null && message.length() > 0) {
							mssg = message.substring(1);
							parent.receiveMessage(0, mssg);
						}
					}

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
